//
//  URL.cpp
//  sad
//
//  Created by Christopher Kemsley on 2/9/12.
//  Copyright (c) 2012 iAppolopolis. All rights reserved.
//

#include "URL.hpp"

#include <iostream>


URL :: URL	()
:	mScheme		()
,	mPath		()
,	mHost		()
	{
	
	}
URL :: URL		( std::string const & inScheme , std::string const & inHost , std::string const & inPath )
:	mScheme		(inScheme)
,	mPath		(inPath)
,	mHost		(inHost)
	{
	
	}
URL :: URL		( std::string const & inURL )
:	mScheme		()
,	mPath		()
,	mHost		()
	{
	_decode(inURL) ;
	}
URL :: URL		( char const * inURL )
:	mScheme		()
,	mPath		()
,	mHost		()
	{
	_decode(std::string(inURL)) ;
	}
URL :: URL		( URL const & inURL )
:	mScheme		(inURL.mScheme)
,	mPath		(inURL.mPath)
,	mHost		(inURL.mHost)
	{
	
	}
URL :: URL		( std::string const & inURL , bool isDir )
:	mScheme		()
,	mPath		()
,	mHost		()
	{
	_decode(inURL,isDir) ;
	}
URL :: URL		( char const * inURL , bool isDir )
:	mScheme		()
,	mPath		()
,	mHost		()
	{
	_decode(std::string(inURL),isDir) ;
	}
URL :: URL		( URL const & inURL , std::string const & inPath )
:	mScheme		(inURL.mScheme)
,	mPath		(inURL.mPath)
,	mHost		(inURL.mHost)
	{
	static std::string	slash	( "/" ) ;
	size_t lastSlash = mPath.rfind(slash) ;
	if ( lastSlash+1 == mPath.size() )
		mPath . append ( inPath ) ;
	else
		mPath . replace ( lastSlash+1 , std::string::npos , inPath ) ;
	}
URL :: URL		( URL const & inURL , char const * inPath )
:	mScheme		(inURL.mScheme)
,	mPath		(inURL.mPath)
,	mHost		(inURL.mHost)
	{
	static std::string	slash	( "/" ) ;
	size_t lastSlash = mPath.rfind(slash) ;
	if ( lastSlash+1 == mPath.size() )
		mPath . append ( inPath ) ;
	else
		mPath . replace ( lastSlash+1 , std::string::npos , inPath ) ;
	}
URL :: URL		( URL const & inURL , std::string const & inPath , bool isDirectory )
:	mScheme		(inURL.mScheme)
,	mPath		(inURL.mPath)
,	mHost		(inURL.mHost)
	{
	static std::string	slash	( "/" ) ;
	size_t lastSlash = mPath.rfind(slash) ;
	if ( lastSlash+1 == mPath.size() )
		mPath . append ( inPath ) ;
	else
		mPath . replace ( lastSlash+1 , std::string::npos , inPath ) ;
	
	if ( isDirectory && mPath[mPath.size()-1] != '/' )
		mPath . append ( slash ) ;
	}
URL :: URL		( URL const & inURL , char const * inPath , bool isDirectory )
:	mScheme		(inURL.mScheme)
,	mPath		(inURL.mPath)
,	mHost		(inURL.mHost)
	{
	static std::string	slash	( "/" ) ;
	size_t lastSlash = mPath.rfind(slash) ;
	if ( lastSlash+1 == mPath.size() )
		mPath . append ( inPath ) ;
	else
		mPath . replace ( lastSlash+1 , std::string::npos , inPath ) ;
	
	if ( isDirectory && mPath[mPath.size()-1] != '/' )
		mPath . append ( slash ) ;
	}


URL const			URL :: absoluteURL () const
	{
	if ( isAbsolute() )
		return *this ;
	return URL ( mScheme , mHost , absolutePath() ) ;
	}
std::string const	URL :: string () const
	{
	std::string		string ( mScheme ) ;
	string.reserve ( mPath.size() + mScheme.size() + mHost.size() + 5 )  ;
	
	string . append ( ":" ) ;
	
	if ( isAbsolute() && mHost.size() != 0 )
		{
		string . append ( "//" ) ;
		string . append ( mHost ) ;
		}
	
	if ( mPath.size() != 0 )
		{
		string . append ( mPath ) ;
		}
	
	return string ;
	}
std::string const	URL :: absoluteString () const
	{
	std::string	path	=	absolutePath() ;
	
	std::string		string ( mScheme ) ;
	string.reserve ( path.size() + mScheme.size() + mHost.size() + 5 )  ;
	
	string . append ( ":" ) ;
	
	if ( mHost.size() != 0 )
		{
		string . append ( "//" ) ;
		string . append ( mHost ) ;
		}
	
	if ( path.size() != 0 )
		{
		string . append ( path ) ;
		}
	
	return string ;
	}
std::string const	URL :: absolutePath () const
	{
	static std::string	slash	( "/" ) ;
	
	if ( isAbsolute() )
		return mPath ;
	
	std::string		basePath	=	_baseURLInfo() [mScheme] [mHost] ;
	
	if ( basePath.size() == 0 )
		return slash + mPath ;
	
	if ( basePath [ basePath.size()-1 ] != '/' )
		basePath . append ( "/" ) ;
	
	basePath . append ( mPath ) ;
	
	return basePath ;
	}
URL const			URL :: directoryURL () const
	{
	if ( isDirectory() )
		return *this ;
	
	URL url ( *this ) ;
	static std::string	slash	( "/" ) ;
	size_t lastSlash = url.mPath.rfind(slash) ;
	url.mPath = url.mPath.substr(0,lastSlash+1) ;
	return url ;
	}

std::string const&	URL :: scheme () const
	{
	return mScheme ;
	}
std::string const&	URL :: host () const
	{
	return mHost ;
	}
std::string const&	URL :: path () const
	{
	return mPath ;
	}
std::string const	URL :: filename () const
	{
	static std::string const	empty ;
	static std::string const	slash	( "/" ) ;
	
	if ( isDirectory() )
		return empty ;
	
	size_t loc = mPath . rfind ( slash ) ;
	
	if ( loc == std::string::npos )
		return mPath ;
	
	return mPath . substr ( loc+1 ) ;
	}
std::string const	URL :: fileExtension () const
	{
	static std::string const	empty ;
	static std::string const	slash	( "/" ) ;
	static std::string const	period	( "." ) ;
	
	if ( isDirectory() )
		return empty ;
	
	size_t	sLoc	=	mPath . rfind ( slash ) ;
	size_t	pLoc	=	mPath . rfind ( period ) ;
	
	// If there's no period, we have no extension
	// "/path/to/this"
	if ( pLoc == std::string::npos )
		return empty ;
	
	// If there is a period but no slash, then we'll return easily
	// "file.xml"
	if ( sLoc == std::string::npos )		// If there's no slash
		return mPath . substr ( pLoc+1 ) ;
	
	// If there're both and the period comes after the slash
	// "path/to/file.xml"
	if ( pLoc > sLoc )
		return mPath . substr ( pLoc+1 ) ;
	
	// Otherwise, the '.' is probably in a folder name
	// "path.to/file"
	return empty ;
	}

bool				URL :: isDirectory () const
	{
	if ( mPath.size() != 0 )
		return mPath[mPath.size()-1]=='/' ;
	return false ;
	}
bool				URL :: isAbsolute () const
	{
	if ( mPath.size() != 0 )
		return mPath[0]=='/' ;
	return false ;
	}
void		URL :: setScheme	( std::string const & value )
	{
	mScheme = value ;
	}
void		URL :: setHost		( std::string const & value )
	{
	mHost = value ;
	}
void		URL :: setPath		( std::string const & value )
	{
	mPath = value ;
	}

URL&	URL :: operator= ( std::string const & inURL )
	{
	_decode ( inURL ) ;
	return *this ;
	}
URL&	URL :: operator= ( char const * inURL )
	{
	_decode ( std::string(inURL) ) ;
	return *this ;
	}
URL&	URL :: operator= ( URL const & inURL )
	{
	mScheme	=	inURL.mScheme ;
	mPath	=	inURL.mPath ;
	mHost	=	inURL.mHost ;
	return *this ;
	}

/* Comparing */
bool	URL :: operator==	( URL const & inURL ) const
	{
	if ( inURL.mScheme != mScheme )
		return false ;
	
	if ( inURL.mHost != mHost )
		return false ;
	
	if ( inURL.mPath != mPath )
		return false ;
	
	return true ;
	}
bool	URL :: operator==	( char const * inURL ) const
	{
	return URL(inURL) == (*this) ;
	}
bool	URL :: operator==	( std::string const & inURL ) const
	{
	return URL(inURL) == (*this) ;
	}
bool	URL :: operator<	( URL const & inURL ) const
	{
	if ( inURL.mScheme < mScheme )
		return true ;
	
	if ( inURL.mHost < mHost )
		return true ;
	
	if ( inURL.mPath < mPath )
		return true ;
	
	return false ;
	}
bool	URL :: operator>	( URL const & inURL ) const
	{
	if ( inURL.mScheme > mScheme )
		return true ;
	
	if ( inURL.mHost > mHost )
		return true ;
	
	if ( inURL.mPath > mPath )
		return true ;
	
	return false ;
	}



void	URL :: _decode					( std::string const & url , bool assumeIsDirectory )
	{
	if ( url.size() == 0 )
		{
		mScheme		=	"" ;
		mPath		=	"" ;
		mHost		=	"" ;
		return ;
		}
	
	_decode_breakout ( url ) ;
	if ( assumeIsDirectory && url[url.size()-1]!='/' )
		mPath . append ( "/" ) ;
	}
void	URL :: _decode_breakout			( std::string const & url )
	{
	/* Some constants */
	static std::string	colon_2		( "://" ) ;
	static std::string	colon_0		( ":" ) ;
	
	size_t	location ;
	
	
	
	/** See if we're fully qualified **/
	
	location	=	url.find(colon_2) ;
	
	if ( location != std::string::npos )
		return _decode_colon_host ( url , location ) ;
	
	
	
	/** See if we're shorthand (no host) **/
	
	location	=	url.find(colon_0) ;
	
	if ( location != std::string::npos )
		return _decode_colon_noHost ( url , location ) ;
	
	
	
	/** See if we're shorthand (no host) **/
	
	return _decode_noColon_noHost ( url ) ;
	}
void	URL :: _decode_colon_host			( std::string const & url , size_t hostStart )
	{
	static std::string slash ( "/" ) ;
	
	mScheme		=	url . substr ( 0 , hostStart ) ;
	while ( url[++hostStart] == '/' ) ;			// Skip all the '/'
	size_t hostEnd = url.find ( slash , hostStart ) ;
	
	mHost = url . substr ( hostStart , hostEnd-hostStart ) ;
	
	if ( hostEnd != std::string::npos )
		mPath = url . substr ( hostEnd ) ;
	else
		mPath = "/" ;
	}
void	URL :: _decode_colon_noHost			( std::string const & url , size_t colonLocation )
	{
	mScheme		=	url . substr ( 0 , colonLocation ) ;
	mPath		=	url . substr ( colonLocation+1 ) ;	// Get the path
	mHost		=	"localhost" ;
	}
void	URL :: _decode_noColon_noHost		( std::string const & url )
	{
	mScheme		=	"file" ;
	mPath		=	url ;
	mHost		=	"localhost" ;
	}


std::ostream& operator<<	( std::ostream & stream , URL const & url )
	{
	stream << url.string() ;
	return stream ;
	}



URL :: SchemeToHTPMap&	URL :: _baseURLInfo ()
	{
	static SchemeToHTPMap m ;
	return m ;
	}


void	URL :: makeBaseURL () const
	{
	if ( isDirectory() )
		_baseURLInfo() [mScheme] [mHost] = mPath ;
	else
		_baseURLInfo() [mScheme] [mHost] = directoryURL().mPath ;
	}
